package program

import (
	"math/big"
	"kumachan/standalone/ctn"
	"kumachan/interpreter/core"
	"kumachan/lang/source"
	"kumachan/lang/typsys"
)


type ExprBasedFunctionValue struct {
	InputArgs  [] *Binding
	InputCtx   [] *Binding
	Output     *Expr
}
func (v *ExprBasedFunctionValue) Call(args ([] core.Object), ctx ([] core.Object), h core.RuntimeHandle) core.Object {
	var eval_ctx = CreateEvalContext(h)
	eval_ctx = eval_ctx.NewCtxBindAll(v.InputArgs, args)
	eval_ctx = eval_ctx.NewCtxBindAll(v.InputCtx, ctx)
	return eval_ctx.Eval(v.Output)
}

type Expr struct {
	Type     typsys.CertainType
	Info     ExprInfo
	Content  ExprContent
}
type ExprInfo struct {
	Location  source.Location
}
func ExprInfoFrom(loc source.Location) ExprInfo {
	return ExprInfo { Location: loc }
}
type ExprContent interface {
	Eval(ctx *EvalContext) core.Object
}

type EvalContext struct {
	bindingValues  ctn.Map[uintptr, core.Object]
	runtimeHandle  core.RuntimeHandle
}
func CreateEvalContext(h core.RuntimeHandle) *EvalContext {
	return &EvalContext {
		bindingValues: makeBindingValuesMap(),
		runtimeHandle: h,
	}
}
func makeBindingValuesMap() ctn.Map[uintptr, core.Object] {
	var cmp = ctn.DefaultCompare[uintptr]()
	return ctn.MakeMap[uintptr, core.Object](cmp)
}
func (ctx *EvalContext) Eval(expr *Expr) core.Object {
	return expr.Content.Eval(ctx)
}
func (ctx *EvalContext) EvalAll(exprList ([] *Expr)) ([] core.Object) {
	var objects = make([] core.Object, len(exprList))
	for i := range exprList {
		objects[i] = ctx.Eval(exprList[i])
	}
	return objects
}
func (ctx *EvalContext) EvalAllAsList(exprList ([] *Expr)) core.List {
	var nodes = make([] core.ListNode, len(exprList))
	for i := range exprList {
		nodes[i].Value = ctx.Eval(exprList[i])
	}
	return core.NodesToList(nodes)
}
func (ctx *EvalContext) EvalBinding(binding *Binding) core.Object {
	var ptr = binding.Pointer()
	var obj, _ = ctx.bindingValues.Lookup(ptr)
	return obj
}
func (ctx *EvalContext) NewCtx() *EvalContext {
	return &EvalContext {
		bindingValues: makeBindingValuesMap(),
		runtimeHandle: ctx.runtimeHandle,
	}
}
func (ctx *EvalContext) NewCtxMatch(p PatternMatching, obj core.Object) *EvalContext {
	var m = ctx.bindingValues
	for _, item := range p {
		var v core.Object
		if item.Index1 == 0 {
			v = obj
		} else {
			var index = (item.Index1 - 1)
			var r = (*obj).(core.Record)
			v = r.Objects[index]
		}
		var k = item.Binding.Pointer()
		m = m.Inserted(k, v)
	}
	return &EvalContext {
		bindingValues: m,
		runtimeHandle: ctx.runtimeHandle,
	}
}
func (ctx *EvalContext) NewCtxBind(binding *Binding, obj core.Object) *EvalContext {
	var m = ctx.bindingValues
	var ptr = binding.Pointer()
	m = m.Inserted(ptr, obj)
	return &EvalContext {
		bindingValues: m,
		runtimeHandle: ctx.runtimeHandle,
	}
}
func (ctx *EvalContext) NewCtxBindAll(bindings ([] *Binding), objects ([] core.Object)) *EvalContext {
	var m = ctx.bindingValues
	for i, b := range bindings {
		var ptr = b.Pointer()
		var obj = objects[i]
		m = m.Inserted(ptr, obj)
	}
	return &EvalContext {
		bindingValues: m,
		runtimeHandle: ctx.runtimeHandle,
	}
}
func (ctx *EvalContext) NewCtxCapture(bindings ([] *Binding)) *EvalContext {
	var new_ctx = ctx.NewCtx()
	var m = &(new_ctx.bindingValues)
	for _, b := range bindings {
		var ptr = b.Pointer()
		var obj, _ = ctx.bindingValues.Lookup(ptr)
		*m = m.Inserted(ptr, obj)
	}
	return new_ctx
}

type PatternMatching ([] PatternMatchingItem)
type PatternMatchingItem struct {
	Binding  *Binding
	Index1   int  // 0 = whole, 1 = .0
}


type Wrapper struct {
	Inner *Expr
}
func (expr Wrapper) Eval(ctx *EvalContext) core.Object {
	return ctx.Eval(expr.Inner)
}

type FunRef struct {
	Function  **Function
	Context   [] *Expr
	Destruct  bool
}
func (expr FunRef) Eval(ctx *EvalContext) core.Object {
	return core.FunctionToLambdaObject (
		(*expr.Function).value,
		expr.Destruct,
		ctx.EvalAll(expr.Context),
		ctx.runtimeHandle,
	)
}

type LocalRef struct {
	Binding *Binding
}
func (expr LocalRef) Eval(ctx *EvalContext) core.Object {
	return ctx.EvalBinding(expr.Binding)
}

type CallExpr struct {
	Callee    *Expr
	Argument  *Expr
}
func (expr CallExpr) Eval(ctx *EvalContext) core.Object {
	var callee = ctx.Eval(expr.Callee)
	var lambda = (*callee).(core.Lambda)
	var argument = ctx.Eval(expr.Argument)
	return lambda.Call(argument)
}

type CallFunRef struct {
	Location   source.Location
	Callee     **Function
	Context    [] *Expr
	Arguments  [] *Expr
}
func (expr CallFunRef) Eval(ctx *EvalContext) core.Object {
	var f = *(expr.Callee)
	var f_ctx = ctx.EvalAll(expr.Context)
	var args = ctx.EvalAll(expr.Arguments)
	var h = ctx.runtimeHandle.WithFrameInfo(core.FrameInfo {
		CalleeName:   f.name,
		CallLocation: expr.Location,
	})
	return f.value.Call(args, f_ctx, h)
}

type Interface struct {
	ConcreteValue  *Expr
	DispatchTable  **DispatchTable
}
func (expr Interface) Eval(ctx *EvalContext) core.Object {
	return core.Obj(core.Interface {
		UnderlyingObject: ctx.Eval(expr.ConcreteValue),
		DispatchTable:    (*expr.DispatchTable).value(),
	})
}

type InterfaceTransformUpward struct {
	Arg   *Expr
	Path  [] int
}
func (expr InterfaceTransformUpward) Eval(ctx *EvalContext) core.Object {
	var arg = ctx.Eval(expr.Arg)
	var I = (*arg).(core.Interface)
	var table = I.DispatchTable
	for _, index := range expr.Path {
		table = table.Children[index]
	}
	return core.Obj(core.Interface {
		UnderlyingObject: I.UnderlyingObject,
		DispatchTable:    table,
	})
}

type InterfaceFromSingleMethod struct {
	MethodValue *Expr
}
func (expr InterfaceFromSingleMethod) Eval(ctx *EvalContext) core.Object {
	var value = ctx.Eval(expr.MethodValue)
	var method core.Function = core.NativeFunction(func(_ ([] core.Object), _ ([] core.Object), _ core.RuntimeHandle) core.Object {
		return value
	})
	var table = &core.DispatchTable {
		Methods:  [] *core.Function { &method },
		Children: nil,
	}
	return core.Obj(core.Interface {
		UnderlyingObject: nil,
		DispatchTable:    table,
	})
}

type None struct {}
func (expr None) Eval(_ *EvalContext) core.Object {
	return nil
}

type Enum int
func (expr Enum) Eval(_ *EvalContext) core.Object {
	var index = int(expr)
	return core.Obj(core.Enum(index))
}

type EnumToInt struct {
	EnumValue  *Expr
}
func (expr EnumToInt) Eval(ctx *EvalContext) core.Object {
	var enum_obj = ctx.Eval(expr.EnumValue)
	var n = big.NewInt(int64(int((*enum_obj).(core.Enum))))
	return core.Obj(core.Int { Value: n })
}

type Union struct {
	Index  int
	Value  *Expr
}
func (expr Union) Eval(ctx *EvalContext) core.Object {
	return core.Obj(core.Union {
		Index:  expr.Index,
		Object: ctx.Eval(expr.Value),
	})
}

type Record struct {
	Values  [] *Expr
}
func (expr Record) Eval(ctx *EvalContext) core.Object {
	return core.Obj(core.Record {
		Objects: ctx.EvalAll(expr.Values),
	})
}

type FieldValue struct {
	Record  *Expr
	Index   int
}
func (expr FieldValue) Eval(ctx *EvalContext) core.Object {
	var record_object = ctx.Eval(expr.Record)
	var record = (*record_object).(core.Record)
	return record.Objects[expr.Index]
}

type ConcreteMethodValue struct {
	This    *Expr
	Path    [] int
	Method  **Function
}
func (expr ConcreteMethodValue) Eval(ctx *EvalContext) core.Object {
	var this = ctx.Eval(expr.This)
	if len(expr.Path) > 0 {
		var I = (*this).(core.Interface)
		var obj = I.UnderlyingObject
		var table = I.DispatchTable
		for _, index := range expr.Path {
			table = table.Children[index]
		}
		this = core.Obj(core.Interface {
			UnderlyingObject: obj,
			DispatchTable:    table,
		})
	}
	var args = [] core.Object { this }
	var h = ctx.runtimeHandle
	return (*expr.Method).value.Call(args, nil, h)
}

type AbstractMethodValue struct {
	Interface  *Expr
	Path       [] int
	Index      int
}
func (expr AbstractMethodValue) Eval(ctx *EvalContext) core.Object {
	var interface_object = ctx.Eval(expr.Interface)
	var I = (*interface_object).(core.Interface)
	var this = I.UnderlyingObject
	var table = I.DispatchTable
	for _, index := range expr.Path {
		table = table.Children[index]
	}
	var method = *(table.Methods[expr.Index])
	var args = [] core.Object { this }
	var h = ctx.runtimeHandle
	return method.Call(args, nil, h)
}

type List struct {
	Items  [] *Expr
}
func (expr List) Eval(ctx *EvalContext) core.Object {
	return core.Obj(ctx.EvalAllAsList(expr.Items))
}

type InteriorRef struct {
	Base     *Expr
	Index    int
	Table    **DispatchTable  // optional, used in dynamic cast
	Kind     InteriorRefKind
	Operand  InteriorRefOperand
}
type InteriorRefKind int
const (
	RK_RecordField InteriorRefKind = iota
	RK_UnionItem
	RK_DynamicCast
)
type InteriorRefOperand int
const (
	RO_Direct InteriorRefOperand = iota
	RO_Lens1
	RO_Lens2
)
func (expr InteriorRef) Eval(ctx *EvalContext) core.Object {
	var base = expr.Base.Content.Eval(ctx)
	switch expr.Kind {
	case RK_RecordField:
		i := expr.Index
		switch expr.Operand {
		case RO_Direct: return core.Lens1FromRecord(base, i)
		case RO_Lens1:  return core.Lens1FromRecordLens1(base, i)
		}
	case RK_UnionItem:
		i := expr.Index
		switch expr.Operand {
		case RO_Direct: return core.Lens2FromUnion(base, i)
		case RO_Lens1:  return core.Lens2FromUnionLens1(base, i)
		case RO_Lens2:  return core.Lens2FromUnionLens2(base, i)
		}
	case RK_DynamicCast:
		t := (*expr.Table).value()
		switch expr.Operand {
		case RO_Direct: return core.Lens2FromInterface(base, t)
		case RO_Lens1:  return core.Lens2FromInterfaceLens1(base, t)
		case RO_Lens2:  return core.Lens2FromInterfaceLens2(base, t)
		}
	}
	panic("something went wrong")
}

type IntLiteral struct {
	Value *big.Int
}
func (expr IntLiteral) Eval(_ *EvalContext) core.Object {
	return core.Obj(core.Int { Value: expr.Value })
}

type CharLiteral struct {
	Value int32
}
func (expr CharLiteral) Eval(_ *EvalContext) core.Object {
	return core.Obj(core.Char(expr.Value))
}

type FloatLiteral struct {
	Value float64
}
func (expr FloatLiteral) Eval(_ *EvalContext) core.Object {
	return core.Obj(core.Float(expr.Value))
}

type BytesLiteral struct {
	Value  [] byte
}
func (expr BytesLiteral) Eval(_ *EvalContext) core.Object {
	return core.Obj(core.Bytes(expr.Value))
}

type StringLiteral struct {
	Value  string
}
func (expr StringLiteral) Eval(_ *EvalContext) core.Object {
	return core.Obj(core.String(expr.Value))
}

type Lambda struct {
	Ctx   [] *Binding
	In    PatternMatching
	Out   *Expr
	Self  *Binding
}
func (expr Lambda) Eval(ctx *EvalContext) core.Object {
	var capture = ctx.NewCtxCapture(expr.Ctx)
	var input_pattern = expr.In
	var output_expr = expr.Out
	var lambda = core.Obj(core.Lambda {
		Call: func(arg core.Object) core.Object {
			var inner = capture.NewCtxMatch(input_pattern, arg)
			return inner.Eval(output_expr)
		},
	})
	if expr.Self != nil {
		capture = capture.NewCtxBind(expr.Self, lambda)
	}
	return lambda
}

type Let struct {
	In   PatternMatching
	Arg  *Expr
	Out  *Expr
}
func (expr Let) Eval(ctx *EvalContext) core.Object {
	var arg = ctx.Eval(expr.Arg)
	var inner = ctx.NewCtxMatch(expr.In, arg)
	return inner.Eval(expr.Out)
}

type If struct {
	Branches  [] IfBranch
}
type IfBranch struct {
	Conds  [] Cond
	Value  *Expr
}
type Cond struct {
	Kind   CondKind
	Match  PatternMatching
	Value  *Expr
}
type CondKind int
const (
	CK_Bool CondKind = iota
	CK_Maybe
	CK_Lens2
)
func (expr If) Eval(ctx *EvalContext) core.Object {
	for _, branch := range expr.Branches {
		var branch_ctx = ctx
		var branch_ok = true
		for _, cond := range branch.Conds {
			var v = ctx.Eval(cond.Value)
			switch cond.Kind {
			case CK_Bool:
				if core.GetBool(v) {
					continue
				}
			case CK_Maybe:
				if v, ok := core.UnwrapMaybe(v); ok {
					branch_ctx = branch_ctx.NewCtxMatch(cond.Match, v)
					continue
				}
			case CK_Lens2:
				if v, ok := core.UnwrapLens2(v); ok {
					branch_ctx = branch_ctx.NewCtxMatch(cond.Match, v)
					continue
				}
			default:
				panic("impossible branch")
			}
			branch_ok = false
			break
		}
		if branch_ok {
			return branch_ctx.Eval(branch.Value)
		}
	}
	panic("bad if expression")
}

type When struct {
	Operand   WhenOperand
	Branches  [] *WhenBranch
}
type WhenBranch struct {
	Match  PatternMatching
	Value  *Expr
}
type WhenOperand struct {
	Kind   WhenOperandKind
	Value  *Expr
}
type WhenOperandKind int
const (
	WO_Union WhenOperandKind = iota
	WO_Enum
)
func (expr When) Eval(ctx *EvalContext) core.Object {
	switch expr.Operand.Kind {
	case WO_Union:
		var operand = ctx.Eval(expr.Operand.Value)
		var u = (*operand).(core.Union)
		var branch = expr.Branches[u.Index]
		var inner = ctx.NewCtxMatch(branch.Match, u.Object)
		return inner.Eval(branch.Value)
	case WO_Enum:
		var operand = ctx.Eval(expr.Operand.Value)
		var index = int((*operand).(core.Enum))
		var branch = expr.Branches[index]
		return ctx.Eval(branch.Value)
	default:
		panic("impossible branch")
	}
}


